﻿using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Controls.Primitives;
using Bling.DSL;
using Bling.WPF;
using Bling.Util;
using Bling.Physics;
using Bling.Shapes;
using Bling.Geom;

namespace Bling.PhysicsTutorial {
  public class StripBlock : PhysicsBlock<PathShape, PathShapeBl, StripBlock>,
    Position2DBody.Is<StripBlock>, PositionMouseAdapter.Is<PathShape, StripBlock> {
    public StripBlock(CanvasBl MainCanvas, World World, int Count) :
      base(World, Count, idx => new PathShapeBl(MainCanvas) {
        ZIndex = 1, Fill = Strip.Sources[idx % Strip.Sources.Length],
      }) {
      new PositionBody<Point, PointBl>.Impl<StripBlock>(this);
      new PositionMouseAdapter.Impl<PathShape, StripBlock>(this);
    }
  }
  public class Strip : PhysicsCanvas {
    public static readonly ImageBrush Dock = new ImageBrush("Resources/dock.jpg".ImageFor(typeof(Strip)));
    public static readonly ImageBrush Desert = new ImageBrush("Resources/Desert.jpg".ImageFor(typeof(Strip)));
    public static readonly ImageBrush Autumn = new ImageBrush("Resources/Autumn.jpg".ImageFor(typeof(Strip)));
    public static readonly ImageBrush Forest = new ImageBrush("Resources/Forest.jpg".ImageFor(typeof(Strip)));
    public static readonly ImageSourceBl Filmstrip = ("Resources/FilmStrip.png".ImageFor(typeof(Strip)));
    public static readonly BrushBl[] Sources = new BrushBl[] {
      Dock, Desert, Autumn, Forest,
    };
    // public static readonly BrushBl FilmStrip = Filmstrip;

    public Strip() : base("Strip", 25) { }
    protected override Action Init(CanvasBl MainCanvas, World World) {
      PointBl P = new PointBl(0, 10);
      // Setup is very similar to Sliders.
      StripBlock block = new StripBlock(MainCanvas, World, 9) { Name = "Strip", };

      // Strength of springs between thumbs.
      var Strength = new LabelSliderBl(MainCanvas) {
        Minimum = 0, Maximum = 1, Value = 1, Width = MainCanvas.Width, LeftBottom = new PointBl(0, MainCanvas.Height), ZIndex = 100,
        LabelName = "Strength",
      };
      // Rest length of springs between thumbs.
      var Length = new LabelSliderBl(MainCanvas) {
        Minimum = 1, Maximum = 200, Value = 100, Width = MainCanvas.Width, LeftBottom = Strength.LeftTop, ZIndex = 100,
        LabelName = "Length",
      };
      var UseShader = new CheckBoxBl(MainCanvas) {
        LeftBottom = Length.LeftTop, ZIndex = 100, Content = "Use Shader", IsChecked = false,
      };
      var UseSoftware = new CheckBoxBl(MainCanvas) {
        LeftBottom = UseShader.RightBottom, ZIndex = 100, Content = "Software", IsChecked = false,
        IsEnabled = UseShader.IsChecked,
      };

      ImageSourceBl border = Filmstrip; // Properties.Resources.FilmStrip.LoadBitmap();
      var FilmStrip = new ImageBrushBl { ImageSource = border, Opacity = 1 };
      var LeftTop0 = new PointBl(4.16d, 28.16d) / border.Size;//the lefttop of the image on film strip
      var size0 = new PointBl(119.68d, 87.04d) / border.Size;//the size of the image on film strip, equals to the size of the red region
      {
        var slider = new SliderBl(MainCanvas) {
          Minimum = -1.5, Maximum = 1, Value = -1.3, LeftTop = new PointBl(0,0),Width = MainCanvas.Width, Visibility = Visibility.Hidden
        };

        
        var MyEffect = EffectBl.Reuse<int, BezierSegment>((input, uv, idx, segment0) => {


          PointBl first = block[idx.Bl()].At.Start;
          BezierSegmentBl segment = segment0;

          PointBl min = first;
          PointBl max = first;
          min = min.Min(segment.Point3);
          max = max.Max(segment.Point3);
          var midPoint = PointBl.Bezier(.5, segment.Points(first));
          min = min.Min(midPoint);
          max = max.Max(midPoint);
          var size = max - min + new PointBl(0, Length);
          var first0 = (first - min) / size;
          var point1 = (segment.Point1 - min) / size;
          var point2 = (segment.Point2 - min) / size;
          var point3 = (segment.Point3 - min) / size;

 //         var xx = (DoubleBl)idx.Bl() / block.Count;

          var p = PointBl.Bezier(uv.X, first0, point1, point2, point3);
          // compute a new Y.
          var y = (uv.Y - p.Y) / (Length.Value / size.Y);
          var uv0 = new PointBl(p.X, y);
          var uv1 = (uv0 - LeftTop0) / size0;
          var temp = (uv.X - uv.Y*1.3 - slider.Value).Abs;
          var flash = ( temp < 0.1).Condition(new Point3DBl(1,1,1) * (1.0 - temp * 10), new Point3DBl(0,0,0));
          var clr = (FilmStrip[uv0].ScR < 0.2).Condition(ColorBl.FromScRGB(FilmStrip[uv0].ScA, FilmStrip[uv0].ScRGB), block[idx.Bl()].At.Fill[uv1]);
          var origA = (input[uv].ScA * clr.ScA).Min(.8);
          clr = clr * block[idx.Bl()].At.IsMouseOver.Condition(1.2, .8d);
          return ColorBl.FromScRGB(origA * 1,( clr.ScRGB * (origA <= 0).Condition(0d, 1d)) + flash * block[idx.Bl()].At.IsMouseOver.Condition(1.0, .0d));
        });

        Func<BezierSegmentBl, int, BezierSegmentBl> F = (segment, idx) => {
          PointBl first = block[idx].At.Start;
          var shape = block[idx].At;
          shape.Segments.Add(segment);
          var dy = new PointBl(0, Length.Value);
          shape.Segments.Add(new LineSegmentBl() { Point = segment.Point3 + dy });
          BezierSegmentBl bottom = new BezierSegmentBl() {
            Point1 = segment.Point2 + dy,
            Point2 = segment.Point3 + dy,
            Point3 = first + dy,
          };
          shape.Segments.Add(bottom);
          shape.Segments.Add(new LineSegmentBl() { Point = first });
          // figure out the left top, left bottom of segment.
          EffectBl effect = MyEffect(idx, segment);
          effect.IsSoftwareRender = UseSoftware.IsChecked;
          shape.Effect = UseShader.IsChecked.Condition(effect, EffectBl.None);
          return segment;
        };
        var snake = new PathShapeBl(MainCanvas) {
          ZIndex = 1, Stroke = { Thickness = 0, Brush = Brushes.Black }
        };
        snake.Start = block[0].At.Start;
        snake.Segments.Add(F(GeometryExtensions.PolygonSegment(0.5, block[0].At.Start, block[0].At.Start, block[1].At.Start, block[2].At.Start), 0));
        for (int i = 0; i < (block.Count - 2); i++) {
          var points = new PointBl[4];
          for (int j = 0; j < points.Length; j++) {
            var k = i + j;
            k = Math.Min(k, block.Count - 1);
            points[j] = block[k].At.Start;
          }
          var segment = F(GeometryExtensions.PolygonSegment(0.5, points), i + 1);
          block[i + 1].At.OnMouseMove = (xx, yy) => {
            slider.Value.Animate.StopOnCompleted(false).Completed((p) => {
              slider.Value.Stop(true); slider.Value = -1.5;
            }).To = 1; 
               return true;
          };
          snake.Segments.Add(segment);
        }
    
      }
      block.ForAll(body => {
        body.Position().Link.To = body.At.Start;
        body.Position().Step = (now, old) => now.VerletStep(0.01, old);
        PointBl.TwoWayX(body.CursorPosition(), 1, Strength.Value / 4d, (at, other, dir) => at.Spring(other, Length));
        PointBl.TwoWayX(body.CursorPosition(), 1, Strength.Value / 3d, (at, other, dir) => {
          var v = (other - at);
          var angle = v.Angle;
          angle = 1d.Bl().Lerp(angle, (dir ? 0 : 1).ToPI());
          return other + angle.ToPoint() * v.Length;
        });
        body.Position().ClampWithRestitution(new PointBl(0, 0), MainCanvas.Size - new PointBl(0, 1) * Length);
      });
      return () => {

        for (int idx = 0; idx < block.Count - 1; idx++) {
          block[idx].At.OnMouseDown = (x, y) => {

            if (y.RightButton == System.Windows.Input.MouseButtonState.Pressed) {
              PointBl[] Points = new PointBl[8];
              PointBl[] Original = new PointBl[8];
              PointBl[] Thumbs = new PointBl[8];

              PathShapeBl shape = new PathShapeBl(MainCanvas) {
                Start = { Now = x.MousePosition }, ZIndex = 2,
                Fill = Brushes.LightPink, Stroke = { Thickness = 2, Brush = Brushes.Black, Dash = { Offset = 1d }, MiterLimit = 1d },
              };

              {
                var i = 0;
                Points[0] = shape.Start;
                for (int j = 0; j < 4; j++) {
                  int sign = (j < 2) ? +1 : -1;
                  OrientationBl orient = (j % 2 == 0) ? Orientation.Horizontal : Orientation.Vertical;

                  var thumbA = Thumbs[i + 1] = new ThumbBl(MainCanvas) {
                    ZIndex = 3, Background = Brushes.Red, CanDrag = false, Visibility = Visibility.Hidden,
                    CenterPosition = { Now = Points[i] + PointBl.Make(orient, 2 * sign) },
                  };
                  var thumbB = Thumbs[(i + 2) % Thumbs.Length] = new ThumbBl(MainCanvas) {
                    ZIndex = 3, Background = Brushes.Blue, CanDrag = false, Visibility = Visibility.Hidden,
                    CenterPosition = { Now = Points[i] + PointBl.Make(orient, 4 * sign) },
                  };
                  // loop around.
                  QuadraticBezierSegmentBl segment = new QuadraticBezierSegmentBl() {
                    Point2 = thumbB,
                  };
                  // assign the array.
                  Points[i + 1] = segment.Point1;
                  if (i + 2 == Points.Length) shape.Start = thumbB;
                  else Points[i + 2] = segment.Point2;

                  var ratio = (thumbA - Points[i]).Select[orient] / ((segment.Point2 - thumbA).Select[orient] + (thumbA - Points[i]).Select[orient]);

                  PointBl.Bezier(ratio, 1, Points[i], segment.Point1, segment.Point2).Bind = thumbA;
                  shape.Segments.Add(segment);

                  i += 2;
                }
                (i == Points.Length).Assert();
              }

              var ll = (x.MousePosition.X > 400).Condition(1, 0);
              var tt = (x.MousePosition.Y > 400).Condition(1, 0);
              for (int i = 0; i < Points.Length; i++) {
                Original[i] = (Thumbs[i].CurrentValue - x.MousePosition) * 100 + x.MousePosition - new PointBl(400 * ll, 400 * tt);
              }

              Thumbs[0].Animate.To = (tt==0).Condition(Original[0 + ll.CurrentValue*2], Original[0]);
              Thumbs[1].Animate.To = (tt==0).Condition(Original[0 + ll.CurrentValue*2], Original[1]  + new PointBl(0, 50*tt));//0.5.Bl().Lerp(Thumbs[0], Thumbs[2]) + new PointBl(0, 50);
              Thumbs[2].Animate.To = (tt==0).Condition(Original[0 + ll.CurrentValue*2], Original[2]);
              Thumbs[6].Animate.To = (tt==1).Condition(Original[4 + (1-ll.CurrentValue)*2], Original[6]);
              Thumbs[5].Animate.To = (tt==1).Condition(Original[4 + (1-ll.CurrentValue)*2], Original[5] - new PointBl(0, 50*(1-tt)));
              Thumbs[4].Animate.To = (tt==1).Condition(Original[4 + (1-ll.CurrentValue)*2], Original[4]);
              Thumbs[7].Animate.To = Original[7] + new PointBl(50*(1+ll), 0);//0.5.Bl().Lerp(Thumbs[0], Thumbs[6]) + new PointBl(50, 0);
              Thumbs[3].Animate.StopOnCompleted(false).Completed((p) => {
                for (int i = 0; i < Points.Length; i++) {
                  Thumbs[i].Stop(true);
                  Thumbs[i].Animate.Tween(t => t.Ease.Bounce.Out).To = Original[i] - new PointBl(40 * ll, 60 * tt);
                }
              }).To = Original[3] - new PointBl(50*(2-ll), 0);//0.5.Bl().Lerp(Thumbs[2], Thumbs[4]) - new PointBl(50, 0);

              DoubleBl minX = Points[0].X;
              DoubleBl minY = Points[0].Y;

              DoubleBl maxX = Points[0].X;
              DoubleBl maxY = Points[0].Y;

              for (int i = 2; i < Points.Length - 1; i += 2) {
                // look at 0, 1, 2
                var q = PointBl.Bezier(.5, Points[i - 2], Points[i - 1], Points[i - 0]);

                minX = minX.Min(Points[i].X).Min(q.X);
                minY = minY.Min(Points[i].Y).Min(q.Y);
                maxX = maxX.Max(Points[i].X).Max(q.X);
                maxY = maxY.Max(Points[i].Y).Max(q.Y);
              }
              {
                var q = PointBl.Bezier(.5, Points[6], Points[7], Points[0]);
                minX = minX.Min(q.X);
                minY = minY.Min(q.Y);
                maxX = maxX.Max(q.X);
                maxY = maxY.Max(q.Y);
              }

              DoubleBl deltaX = maxX - minX;
              DoubleBl deltaY = maxY - minY;

              Func<PointBl, PointBl> Relative = (p) =>
                //        (p - shape.LeftTop) / shape.Size;
                (p - new PointBl(minX, minY)) / new PointBl(deltaX, deltaY);

              var slider = new SliderBl(MainCanvas) {
                Visibility = Visibility.Hidden, Minimum = 0.0, Maximum = 1.0, Value = 1.0
              };
              shape.Effect.Custom = (input, uv) => {
                var p0 = Relative(Points[0]);
                var p2 = Relative(Points[2]);
                var p4 = Relative(Points[4]);
                var p6 = Relative(Points[6]);


                var right = PointBl.Bezier(uv.Y, p2, Relative(Points[3]), p4);
                var left = PointBl.Bezier(1.0 - uv.Y, p6, Relative(Points[7]), p0);

                var top = PointBl.Bezier(uv.X, p0, Relative(Points[1]), p2);
                var bot = PointBl.Bezier(1 - uv.X, p4, Relative(Points[5]), p6);

                var newX = (uv - left).Length / ((uv - right).Length + (uv - left).Length);
                var newY = (uv - top).Length / ((bot - uv).Length + (uv - top).Length);
                var alpha = slider.Value * input[uv].ScA;
                return ColorBl.FromScRGB(alpha, block[idx].At.Fill[new PointBl(newX, newY)].ScRGB * alpha);
              };
              slider.Value.Animate.Delay(2000).To = 0;

            }
            return true;
          };
        }
      };
    }
  }
}
    


